<?php
/**
 * User: Mykola B.
 * Date: 3/3/13
 * Time: 2:07 PM
 */
namespace geo {
    class ClusterCollection implements \Iterator
    {
        /**
         * @var Cluster[]
         */
        public $clusters = array();

        /**
         * @var int
         */
        private $distThreshold;
        /**
         * @var int
         */
        private $locationsThreshold;

        /**
         * @var int
         */
        private $position = 0;

        /**
         * @param Point[] $points
         * @param int $distThreshold meters
         * @param int $locationsThreshold cardinality of Points
         */
        public function __construct(PointCollection $points, $distThreshold, $locationsThreshold) {
            $this->position = 0;
            $this->distThreshold = $distThreshold;
            $this->locationsThreshold = $locationsThreshold;
            foreach ($points as $key => $point) {
                // attempt to make a sequential cluster
                try {
                    $this->push($this->createCluster($key, $point, $points));
                // on fail make a single point cluster
                } catch (ClusterCollectionException $e) {
                    if ($e->getCode() == ClusterCollectionException::NOT_ENOUGH_POINTS) {
                        $this->push(new Cluster($point));
                    }
                }
            }
        }

        public function push(Cluster $cluster) {
            array_push($this->clusters, $cluster);
        }

        /**
         * Makes a cluster of points that are closer than proximity threshold for a given point.
         * @param Point $point
         * @param PointCollection $points
         */
        private function createCluster($pointKey, Point $point, PointCollection $points) {
            $cluster = new Cluster($point);
            foreach ($points as $key => $otherPoint) {
                // skip to the point after the given one
                if ($key <= $pointKey) {
                    continue;
                }
                if ($point->distanceM($otherPoint) < $this->distThreshold) {
                    $cluster->push($otherPoint);
                } else {
                    break;
                }
            }
            if (count($cluster) < $this->locationsThreshold) {
                throw new ClusterCollectionException('Not enough points', 0);
            }
            return $cluster;
        }

        public function rewind() {
            $this->position = 0;
        }

        public function current() {
            return $this->clusters[$this->position];
        }

        public function key() {
            return $this->position;
        }

        public function next() {
            ++$this->position;
        }

        public function valid() {
            return isset($this->clusters[$this->position]);
        }
    }

    class ClusterCollectionException extends \Exception {
        const NOT_ENOUGH_POINTS = 0;
    }
}
